001 /*
002 * Copyright 2004 Stephen J. McConnell.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
013 * implied.
014 *
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package net.dpml.util;
020
021 import java.io.PrintWriter;
022 import java.io.StringWriter;
023 import java.lang.reflect.Method;
024 import java.util.StringTokenizer;
025
026 /**
027 * General utilities supporting the packaging of exception messages.
028 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
029 * @version 1.0.2
030 */
031 public final class ExceptionHelper
032 {
033 // ------------------------------------------------------------------------
034 // static
035 // ------------------------------------------------------------------------
036
037 /**
038 * Returns the exception and causal exceptions as a formatted string.
039 * @param e the exception
040 * @return String the formatting string
041 */
042 public static String packException( final Throwable e )
043 {
044 return packException( null, e );
045 }
046
047 /**
048 * Returns the exception and causal exceptions as a formatted string.
049 * @param e the exception
050 * @param stack TRUE to generate a stack trace
051 * @return String the formatting string
052 */
053 public static String packException( final Throwable e, boolean stack )
054 {
055 return packException( null, e, stack );
056 }
057
058
059 /**
060 * Returns the exception and causal exceptions as a formatted string.
061 * @param message the header message
062 * @param e the exception
063 * @return String the formatting string
064 */
065 public static String packException( final String message, final Throwable e )
066 {
067 return packException( message, e, false );
068 }
069
070 /**
071 * Returns the exception and causal exceptions as a formatted string.
072 * @param message the header message
073 * @param e the exception
074 * @param stack TRUE to generate a stack trace
075 * @return String the formatting string
076 */
077 public static String packException(
078 final String message, final Throwable e, boolean stack )
079 {
080 StringBuffer buffer = new StringBuffer();
081 packException( buffer, 0, message, e, stack );
082 buffer.append( getLine( END ) );
083 return buffer.toString();
084 }
085
086
087 /**
088 * Returns the exception and causal exceptions as a formatted string.
089 * @param message the header message
090 * @param e the exceptions
091 * @param stack TRUE to generate a stack trace
092 * @return String the formatting string
093 */
094 public static String packException(
095 final String message, final Throwable[] e, boolean stack )
096 {
097 final String lead = COMPOSITE + "(" + e.length + " entries) ";
098 StringBuffer buffer = new StringBuffer( getLine( lead ) );
099 if( null != message )
100 {
101 buffer.append( message );
102 buffer.append( "\n" );
103 }
104 for( int i=0; i < e.length; i++ )
105 {
106 packException( buffer, i + 1, null, e[i], stack );
107 }
108 buffer.append( getLine( END ) );
109 return buffer.toString();
110 }
111
112 // ------------------------------------------------------------------------
113 // static implementation
114 // ------------------------------------------------------------------------
115
116 /**
117 * Line separator character.
118 */
119 private static final String LINE_SEPARATOR =
120 System.getProperty( "line.separator" );
121
122 /**
123 * Header token.
124 */
125 private static final String HEADER = "----";
126
127 /**
128 * Exception token.
129 */
130 private static final String EXCEPTION = HEADER + " exception report ";
131
132 /**
133 * Composite token.
134 */
135 private static final String COMPOSITE = HEADER + " composite report ";
136
137 /**
138 * Runtime token.
139 */
140 private static final String RUNTIME = HEADER + " runtime exception report ";
141
142 /**
143 * Error token.
144 */
145 private static final String ERROR = HEADER + " error report ";
146
147 /**
148 * Cause token.
149 */
150 private static final String CAUSE = HEADER + " cause ";
151
152 /**
153 * Trace token.
154 */
155 private static final String TRACE = HEADER + " stack trace ";
156
157 /**
158 * End token.
159 */
160 private static final String END = "";
161
162 /**
163 * Nominal width of character display.
164 */
165 private static final int WIDTH = 80;
166
167 /**
168 * Returns the exception and causal exceptions as a formatted string.
169 * @param buffer the string buffer
170 * @param j the causal message sequence
171 * @param message the header message
172 * @param e the exception
173 * @param stack TRUE to generate a stack trace
174 */
175 private static void packException(
176 final StringBuffer buffer, int j, final String message, final Throwable e, boolean stack )
177 {
178 if( e instanceof Error )
179 {
180 buffer.append( getLine( ERROR, j ) );
181 }
182 else if( e instanceof RuntimeException )
183 {
184 buffer.append( getLine( RUNTIME, j ) );
185 }
186 else
187 {
188 buffer.append( getLine( EXCEPTION, j ) );
189 }
190
191 if( null != message )
192 {
193 buffer.append( message );
194 buffer.append( "\n" );
195 }
196
197 if( e == null )
198 {
199 return;
200 }
201
202 buffer.append( "Exception: " + e.getClass().getName() + "\n" );
203 if( null != e.getMessage() )
204 {
205 buffer.append( "Message: " + e.getMessage() + "\n" );
206 }
207 packCause( buffer, getCause( e ) ).toString();
208 Throwable root = getLastThrowable( e );
209 if( ( root != null ) && stack )
210 {
211 buffer.append( getLine( TRACE ) );
212 String[] trace = captureStackTrace( root );
213 for( int i = 0; i < trace.length; i++ )
214 {
215 buffer.append( trace[i] + "\n" );
216 }
217 }
218 }
219
220 /**
221 * Pack a causal exception.
222 * @param buffer the buffer to pack the exception report
223 * @param cause the causal exception to pack
224 * @return the buffer
225 */
226 private static StringBuffer packCause( StringBuffer buffer, Throwable cause )
227 {
228 if( cause == null )
229 {
230 return buffer;
231 }
232 buffer.append( getLine( CAUSE ) );
233 buffer.append( "Exception: " + cause.getClass().getName() + "\n" );
234 buffer.append( "Message: " + cause.getMessage() + "\n" );
235 return packCause( buffer, getCause( cause ) );
236 }
237
238 /**
239 * Return the last throwable in the chain.
240 * @param exception the exception to extract the last throwable from
241 * @return the initiating cause
242 */
243 private static Throwable getLastThrowable( Throwable exception )
244 {
245 Throwable cause = getCause( exception );
246 if( cause != null )
247 {
248 return getLastThrowable( cause );
249 }
250 else
251 {
252 return exception;
253 }
254 }
255
256 /**
257 * Get a causal exception using reflection.
258 * @param exception the exception
259 * @return the causal exception
260 */
261 private static Throwable getCause( Throwable exception )
262 {
263 if( null == exception )
264 {
265 return null;
266 }
267
268 try
269 {
270 Class clazz = exception.getClass();
271 Method method = clazz.getMethod( "getCause", new Class[0] );
272 return (Throwable) method.invoke( exception, new Object[0] );
273 }
274 catch( Throwable e )
275 {
276 return null;
277 }
278 }
279
280 /**
281 * Captures the stack trace associated with this exception.
282 *
283 * @param throwable a <code>Throwable</code>
284 * @return an array of Strings describing stack frames.
285 */
286 private static String[] captureStackTrace( final Throwable throwable )
287 {
288 final StringWriter sw = new StringWriter();
289 throwable.printStackTrace( new PrintWriter( sw, true ) );
290 return splitString( sw.toString(), LINE_SEPARATOR );
291 }
292
293 /**
294 * Splits the string on every token into an array of stack frames.
295 *
296 * @param string the string to split
297 * @param onToken the token to split on
298 * @return the resultant array
299 */
300 private static String[] splitString( final String string, final String onToken )
301 {
302 final int offset = 4;
303 final StringTokenizer tokenizer = new StringTokenizer( string, onToken );
304 final String[] result = new String[tokenizer.countTokens()];
305
306 for( int i = 0; i < result.length; i++ )
307 {
308 String token = tokenizer.nextToken();
309 if( token.startsWith( "\tat " ) )
310 {
311 result[i] = token.substring( offset );
312 }
313 else
314 {
315 result[i] = token;
316 }
317 }
318
319 return result;
320 }
321
322 /**
323 * Return a line of '-' characters.
324 * @param lead the lead
325 * @return the line
326 */
327 private static String getLine( String lead )
328 {
329 return getLine( lead, 0 );
330 }
331
332 /**
333 * Get a line of '-' characters with padded offset.
334 * @param lead the leading characters
335 * @param count the number of characters to fill
336 * @return the filled out line
337 */
338 private static String getLine( String lead, int count )
339 {
340 StringBuffer buffer = new StringBuffer( lead );
341 int q = 0;
342 if( count > 0 )
343 {
344 String v = "" + count + " ";
345 buffer.append( "" + count );
346 buffer.append( " " );
347 q = v.length() + 1;
348 }
349 int j = WIDTH - ( lead.length() + q );
350 for( int i=0; i < j; i++ )
351 {
352 buffer.append( "-" );
353 }
354 buffer.append( "\n" );
355 return buffer.toString();
356 }
357
358 /**
359 * Disabled.
360 */
361 private ExceptionHelper()
362 {
363 // disable
364 }
365 }